﻿using System;
using System.Configuration;
using System.Web;
using System.Web.Routing;

namespace SharePointMvc
{
    public static class RoutingManager
    {
        private const string routeCacheItemSessionKey = "routeCacheItem";
        
        public static RoutingCongfigurationSection GetRouteConfigurationSection()
        {
            var rountingSectionName = ConfigurationManager.AppSettings["customRoutingSectionName"];
            if(rountingSectionName == null)
            {
                throw new ConfigurationErrorsException("Unable to locate the AppSetting item 'customRoutingSectionName'");
            }

            var configSection = ConfigurationManager.GetSection(rountingSectionName) as RoutingCongfigurationSection;
            if (configSection == null)
            {
                throw new ConfigurationErrorsException("Unable to load the SharePointMvc configuration section");
            }
            return configSection;
        }

        internal static RouteData GetRouteData(HttpContextWrapper httpContext)
        {
            var section = GetRouteConfigurationSection();
            var accessor = section.RouteAccessor;
            
            switch (accessor)
            {
                case RouteAccess.Cache:
                default:
                    var routeCacheItem = httpContext.Session[routeCacheItemSessionKey] as RouteCacheItem;
                    if (routeCacheItem == null)
                    {
                        throw new ApplicationException("Unable to retrieve the route item from the cache");
                    }
                    return BuildRouteData(routeCacheItem);
                    //case RouteAccess.Inspect:
                    //    routeData = DetermineRoute(httpContext);
                    //    break;
                    //default:
                    //    throw new ArgumentOutOfRangeException();
            }
        }
        
        public static void ProcessRequest(HttpContext httpContext)
        {
            try
            {
                var httpContextWrapper = new HttpContextWrapper(httpContext);
                var routeData = GetRouteData(httpContextWrapper);
                var requestContext = new RequestContext(httpContextWrapper, routeData);
                var mvcHandler = new MvcHandler(requestContext);
                mvcHandler.ProcessRequest2(httpContextWrapper);
            }
            catch (Exception exception)
            {
                AddExceptionToContext(httpContext, exception);
                throw;
            }
        }

        private static void AddExceptionToContext(HttpContext httpContext, Exception exception)
        {
            //HACK: to stop 404 always being shown when the controller is not found
            if (exception is HttpException && exception.Message.Contains("' could not be found "))
            {
                httpContext.AddError(new Exception(exception.Message));
            }
            else
            {
                httpContext.AddError(exception);
            }
        }

        public static void ProcessStandardRequest(HttpContext httpContext)
        {
            try
            {
                var httpContextWrapper = new HttpContextWrapper(httpContext);
                Route route;
                var routeData = GetRouteDataAndRoute(httpContextWrapper, out route);
                var requestContext = new RequestContext(httpContextWrapper, routeData);
                var mvcHandler = new MvcHandler(requestContext);
                mvcHandler.ProcessRequest2(httpContextWrapper);
            }
            catch (Exception exception)
            {
                AddExceptionToContext(httpContext, exception);
                throw;
            }
        }
        
        public static void AddRouteToCache(HttpContext httpContext)
        {
            var section = GetRouteConfigurationSection();
            if (section.RouteAccessor == RouteAccess.Cache)
            {
                var httpContextWrapper = new HttpContextWrapper(httpContext);
                Route route;
                var routeData = GetRouteDataAndRoute(httpContextWrapper, out route);
                httpContext.Session.Add(routeCacheItemSessionKey, new RouteCacheItem(routeData, route));
            }
            else
            {
                throw new Exception("The RoutingSection routeAccessor node is not set to Cache in the web.config");
            }
        }

        public static void ClearRouteCache(HttpContextWrapper httpContext)
        {
            var section = GetRouteConfigurationSection();

            if(section.RouteAccessor == RouteAccess.Cache)
            {
                httpContext.Session[routeCacheItemSessionKey] = null;
            }
        }

        public static RouteData GetRouteDataAndRoute(HttpContextBase httpContext, out Route route)
        {
            using (RouteTable.Routes.GetReadLock())
            {
                foreach (Route myRoute in RouteTable.Routes)
                {
                    var routeData = myRoute.GetRouteData(httpContext);
                    if (routeData != null)
                    {
                        route = myRoute;
                        return routeData;
                    }
                }
            }
            throw new Exception("Unable to retrieve RouteData!");
        }

        public static void BuildAndRegisterRoutesFromConfig(IRouteHandler routeHandler)
        {
            var section = GetRouteConfigurationSection();
            using (RouteTable.Routes.GetWriteLock())
            {
                foreach (ConfigurationRoute myRoute in section.ConfigurationRoutes)
                {
                    RouteTable.Routes.Add(new Route(myRoute.Url, routeHandler));
                }
            }
        }

        public static void BuildAndRegisterRoutesFromConfig()
        {
            var section = GetRouteConfigurationSection();
            using (RouteTable.Routes.GetWriteLock())
            {
                foreach (ConfigurationRoute myRoute in section.ConfigurationRoutes)
                {
                    RouteTable.Routes.Add(new Route(myRoute.Url, new RouteHandler()));
                }
            }
        }

        public static bool UseDefaultHttpHandler(HttpContext httpContext)
        {
            try
            {
                var routingCongfigurationSection = GetRouteConfigurationSection();
                return httpContext.Request[routingCongfigurationSection.UseDefaultRouteKey] != null;
            }
            catch (Exception exception)
            {
                AddExceptionToContext(httpContext, exception);
                throw;
            }
        }
        

        #region Private Methods
        private static RouteData BuildRouteData(RouteCacheItem routeCacheItem)
        {
            var route = GetRouteFromRouteTable(routeCacheItem.Url);
            var routeData = new RouteData(route, route.RouteHandler);

            foreach (var keyValuePair in routeCacheItem.Dictionary)
            {
                routeData.Values.Add(keyValuePair.Key, keyValuePair.Value);
            }
            return routeData;
        }

        private static Route GetRouteFromRouteTable(string url)
        {
            using (RouteTable.Routes.GetReadLock())
            {
                foreach (Route route in RouteTable.Routes)
                {
                    if(route.Url == url)
                    {
                        return route;
                    }
                }
            }
            throw new Exception("Could not find Route.");
        }

        #endregion

    }
}